﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SCG.Prism
{
    public partial class GameCoordinator : Component
    {
        //events
        public event EventHandler<OrdersEventArgs> GetOrders;
        public event EventHandler<ProcessOrdersEventArgs> ProcessOrders;
        public event EventHandler<MessageEventArgs> ProcessChanges;
        public event EventHandler<EventArgs> ImpulseProcessed;

        //constructors
        public GameCoordinator()
        {
            InitializeComponent();
        }
        public GameCoordinator(IContainer container)
        {
            container.Add(this);
            InitializeComponent();
        }

        //The PrismConnection component
        public PrismConnection PrismConnection
        {
            get
            {
                return _prism;
            }
            set
            {
                //de-couple event handlers
                if (_prism != null)
                {
                    _prism.DataMessageReceived -= prism_DataMessageReceived;
                    _prism.UserLeftRoom -= prism_UserLeftRoom;
                }

                //assign
                _prism = value;

                //couple event handlers
                if (_prism != null)
                {
                    _prism.DataMessageReceived += prism_DataMessageReceived;
                    _prism.UserLeftRoom += prism_UserLeftRoom;
                }
            }
        }

        //Is it active?
        public bool Active
        {
            get
            {
                return _active;
            }
        }

        //Is it paused?
        public bool Paused { get; set; }

        //Current impulse number
        public int Impulse { get; set; }

        //MS between impulses
        public int ImpulseMS
        {
            get
            {
                return _impulseMS;
            }
            set
            {
                if (value > 0)
                    _impulseMS = value;
            }
        }

        //Is the current player the host?
        public bool IsHost
        {
            get
            {
                if (_prism == null)
                    return true;
                if (!_prism.LoggedIn)
                    return true;
                if (_prism.ThisUser == null)
                    return true;

                //allow for correct result even before subject starts
                lock (_activeMembers)
                {
                    if (_activeMembers.Count == 0 || !_activeMembers.Contains(_prism.ThisUser))
                    {
                        if (_prism.Users.Count == 0)
                            return true;
                        else
                            return _prism.ThisUser == _prism.Users[0];
                    }
                    else
                        return _prism.ThisUser == _activeMembers[0];
                }
            }
        }

        //Access the active members
        public List<PrismUser> ActiveParticipants
        {
            get
            {
                return _activeMembers;
            }
        }

        //Start the session with the specified impulse number
        public void Start(int impulse)
        {
            if (_prism == null)
                throw new InvalidOperationException("PrismConnection component must be assigned");

            _active = true;
            Impulse = impulse;
            _orders.Clear();
            _changes = null;

            //add active members, allow it to work in offline mode
            lock (_activeMembers)
            {
                _activeMembers.Clear();
            }

            if (_prism.Connected)
            {
                try
                {
                    lock (_activeMembers)
                    {
                        foreach (PrismUser user in _prism.Users)
                            _activeMembers.Add(user);
                    }
                }
                catch
                {
                }
            }
            lock (_activeMembers)
            {
                if (_activeMembers.Count == 0)
                {
                    PrismUser dummy = new PrismUser();
                    _prism.ThisUser = dummy;
                    _activeMembers.Add(dummy);
                }
            }

            //launch a thread to handle submission of orders at assigned intervals
            _gameThread = new Thread(new ThreadStart(GameThreadExecute));
            _gameThread.Name = "GameCoordinator";
            _gameThread.IsBackground = true;
            _gameThread.Start();
        }

        //Stop the session
        public void Stop()
        {
            _active = false;
        }

        //Remove a player
        public void RemoveParticipant(PrismUser user)
        {
            if (user == null)
                return;
            bool wasHost = IsHost;

            if (_orders.ContainsKey(user))
                _orders.Remove(user);

            lock (_activeMembers)
            {
                if (_activeMembers.Contains(user))
                    _activeMembers.Remove(user);
            }

            //If it's the host, send a dummy Orders so new host can resume game
            if (IsHost)
                _newHost = !wasHost;
        }

        //private members
        private PrismConnection _prism = null;
        private bool _active = false;
        private int _impulseMS = 1000;
        private List<PrismUser> _activeMembers = new List<PrismUser>();
        private Dictionary<PrismUser, string> _orders = new Dictionary<PrismUser, string>();
        private string _changes = null;
        private Thread _gameThread = null;
        private object _lock = new object();
        private bool _newHost = false;

        //user has left the room, allow session to continue
        private void prism_UserLeftRoom(object sender, PrismUserEventArgs e)
        {
            RemoveParticipant(e.User);
        }

        //hook into incoming PrismServer data messages, these can be order submissions or state changes
        private void prism_DataMessageReceived(object sender, PrismUserMessageEventArgs e)
        {
            lock (_lock)
            {
                //process orders received
                if (e.Msg.StartsWith("ORDERS|"))
                    _orders[e.User] = e.Msg.Substring(7);

                //process change string
                else if (e.Msg.StartsWith("CHANGES|"))
                    _changes = e.Msg.Substring(8);
            }
        }

        //primary game thread
        private void GameThreadExecute()
        {
            DateTime wait = DateTime.Now.AddMilliseconds(ImpulseMS);
            while (Active)
            {
                //delay until next impulse             
                while (DateTime.Now < wait && Active)
                    Thread.Sleep(10);

                //pause
                while (Paused && Active)
                    Thread.Sleep(10);
                if (!Active)
                    break;

                //get current orders and submit them
                string orders = "";
                if (GetOrders != null)
                {
                    OrdersEventArgs oea = new OrdersEventArgs();
                    GetOrders(this, oea);
                    orders = oea.Orders;
                }

                //Host submits directly, others send their orders to host
                lock (_lock)
                {
                    if (IsHost)
                        _orders[_prism.ThisUser] = orders;
                    else if (_prism != null)
                        _prism.SendData("ORDERS|" + orders);
                }

                //wait until all orders are in, or change message comes back
                bool processed = false;
                while (!processed && Active)
                {
                    lock (_lock)
                    {
                        if (IsHost)
                        {
                            //if this player is the new host, just proceed for this impulse
                            if (_orders.Count >= _activeMembers.Count || _newHost)
                            {
                                _newHost = false;

                                //process impulse, send change string to other players
                                if (ProcessOrders != null)
                                {
                                    List<string> orderList = new List<string>();
                                    foreach (KeyValuePair<PrismUser, string> kvp in _orders)
                                        orderList.Add(kvp.Value);
                                    ProcessOrdersEventArgs oea = new ProcessOrdersEventArgs(orderList);
                                    ProcessOrders(this, oea);

                                    //send the changes to other clients                                    
                                    if (_prism != null)
                                        _prism.SendData("CHANGES|" + oea.StateChanges);
                                }
                                processed = true;
                            }
                        }
                        else
                        {
                            //other players wait to process the change string
                            if (_changes != null)
                            {
                                if (ProcessChanges != null)
                                    ProcessChanges(this, new MessageEventArgs(_changes));
                                _changes = null;
                                processed = true;
                            }
                        }
                    }

                    if (!processed)
                        Thread.Sleep(10);
                    if (!Active)
                        break;
                }

                //Allow UI to respond            
                lock (_lock)
                {
                    _orders.Clear();
                }
                if (ImpulseProcessed != null)
                {
                    try
                    {
                        PrismConnection.ParentControl.Invoke(ImpulseProcessed, this, EventArgs.Empty);
                    }
                    catch
                    {
                        //in case window was destroyed during shut down
                    }
                }

                //determine next impulse end time
                if (Active)
                {
                    Impulse++;
                    wait = wait.AddMilliseconds(ImpulseMS);
                    if (wait < DateTime.Now)
                        wait = DateTime.Now;
                }
            }
        }    
    }
}
